//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//

package com.strangeberry.jmdns;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


/**
 * A table of DNS entries. This is a hash table which can handle multiple
 * entries with the same name.
 * <p>
 * Storing multiple entries with the same name is implemented using a linked
 * list of <code>CacheNode</code>'s.
 * <p>
 * The current implementation of the API of DNSCache does expose the cache nodes
 * to clients. Clients must explicitly deal with the nodes when iterating over
 * entries in the cache. Here's how to iterate over all entries in the cache:
 * 
 * <pre>
 *  
 *  for (Iterator i=dnscache.iterator(); i.hasNext(); ) {
 *     for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n.next()) {
 *        DNSEntry entry = n.getValue();
 *        ...do something with entry...
 *     }
 *  }
 *   
 * </pre>
 * 
 * <p>
 * And here's how to iterate over all entries having a given name:
 * 
 * <pre>
 *  
 *  for (DNSCache.CacheNode n = (DNSCache.CacheNode) dnscache.find(name); n != null; n.next()) {
 *      DNSEntry entry = n.getValue();
 *      ...do something with entry...
 *  }
 *   
 * </pre>
 * 
 * @author Arthur van Hoff, Werner Randelshofer
 * @version %I%, %G%
 */
class DNSCache {
//  private static Logger logger = Logger.getLogger(DNSCache.class.toString());

    // Implementation note:
    // We might completely hide the existence of CacheNode's in a future version
    // of DNSCache. But this will require to implement two (inner) classes for
    // the iterators that will be returned by method <code>iterator()</code> and
    // method <code>find(name)</code>.
    // Since DNSCache is not a public class, it does not seem worth the effort
    // to clean its API up that much.

    // [PJYF Oct 15 2004] This should implements Collections that would be amuch
    // cleaner implementation

    /**
     * The number of DNSEntry's in the cache.
     */
    private int size;

    /**
     * The hashtable used internally to store the entries of the cache. Keys are
     * instances of String. The String contains an unqualified service name.
     * Values are linked lists of CacheNode instances.
     */
    private Map<String, CacheNode> cacheNodeMap;

    /**
     * Cache nodes are used to implement storage of multiple DNSEntry's of the
     * same name in the cache.
     */
    public static class CacheNode {
//      private static Logger logger = Logger.getLogger(CacheNode.class.toString());

        private DNSEntry value;

        private CacheNode next;

        public CacheNode(DNSEntry value) {
            this.value = value;
        }

        public CacheNode next() {
            return next;
        }

        public DNSEntry getValue() {
            return value;
        }
    }

    /**
     * Create a table with a given initial size.
     */
    public DNSCache(final int size) {
        cacheNodeMap = new HashMap<String, CacheNode>(size);
    }

    /**
     * Clears the cache.
     */
    public synchronized void clear() {
        cacheNodeMap.clear();
        size = 0;
    }

    /**
     * Adds an entry to the table.
     */
    public synchronized void add(final DNSEntry entry) {
//logger.log("DNSCache.add(" + entry.getName() + ")");
        CacheNode newValue = new CacheNode(entry);
        CacheNode node = cacheNodeMap.get(entry.getName());
        if (node == null) {
            cacheNodeMap.put(entry.getName(), newValue);
        } else {
            newValue.next = node.next;
            node.next = newValue;
        }
        size++;
    }

    /**
     * Remove a specific entry from the table. Returns true if the entry was
     * found.
     */
    public synchronized boolean remove(DNSEntry entry) {
        CacheNode node = cacheNodeMap.get(entry.getName());
        if (node != null) {
            if (node.value == entry) {
                if (node.next == null) {
                    cacheNodeMap.remove(entry.getName());
                } else {
                    cacheNodeMap.put(entry.getName(), node.next);
                }
                size--;
                return true;
            }

            CacheNode previous = node;
            node = node.next;
            while (node != null) {
                if (node.value == entry) {
                    previous.next = node.next;
                    size--;
                    return true;
                }
                previous = node;
                node = node.next;
            }
        }
        return false;
    }

    /**
     * Get a matching DNS entry from the table (using equals). Returns the entry
     * that was found.
     */
    public synchronized DNSEntry get(DNSEntry entry) {
        for (CacheNode node = find(entry.getName()); node != null; node = node.next) {
            if (node.value.equals(entry)) {
                return node.value;
            }
        }
        return null;
    }

    /**
     * Get a matching DNS entry from the table.
     */
    public synchronized DNSEntry get(String name, int type, int clazz) {
        for (CacheNode node = find(name); node != null; node = node.next) {
            if (node.value.type == type && node.value.clazz == clazz) {
                return node.value;
            }
        }
        return null;
    }

    /**
     * Iterates over all cache nodes. The iterator returns instances of
     * DNSCache.CacheNode. Each instance returned is the first node of a linked
     * list. To retrieve all entries, one must iterate over this linked list.
     * See code snippets in the header of the class.
     */
    public Iterator<CacheNode> iterator() {
        return Collections.unmodifiableCollection(cacheNodeMap.values()).iterator();
    }

    /**
     * Iterate only over items with matching name. Returns an instance of
     * DNSCache.CacheNode or null. If an instance is returned, it is the first
     * node of a linked list. To retrieve all entries, one must iterate over
     * this linked list.
     */
    public synchronized CacheNode find(String name) {
        return cacheNodeMap.get(name);
    }

    /**
     * List all entries for debugging.
     */
    public synchronized void print() {
        for (Iterator<CacheNode> i = iterator(); i.hasNext();) {
            for (CacheNode n = i.next(); n != null; n = n.next) {
                System.out.println(n.value);
            }
        }
    }

    public synchronized String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("\t---- cache ----");
        for (Iterator<CacheNode> i = iterator(); i.hasNext();) {
            for (CacheNode n = i.next(); n != null; n = n.next) {
                sb.append("\n\t\t" + n.value);
            }
        }
        return sb.toString();
    }
}

/* */
